#include "uhttpclient.h"

#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QEventLoop>
#include <QTimer>
#include <QFileInfo>
#include <QHttpMultiPart>

class UHttpClientPrivate
{
    Q_DECLARE_PUBLIC(UHttpClient)
public:
    UHttpClientPrivate(UHttpClient* parent)
        : q_ptr(parent), networkManager(new QNetworkAccessManager)
    {
        qInfo() << "supportedSchemes" << networkManager->supportedSchemes();
    }
    ~UHttpClientPrivate() {}

    bool handleSyncRequest(const QString &method, const QNetworkRequest &request, const QByteArray &data, QByteArray &result);

    bool handleASyncRequest(const QString &method, const QNetworkRequest &request,
                            const QByteArray &data, std::function<void (int, QByteArray)> resopnse);

    UHttpClient* q_ptr = nullptr;
private:
    QScopedPointer<QNetworkAccessManager> networkManager;
    int m_timeOut = -1;
    QNetworkReply* currentReply = nullptr;
};

bool UHttpClientPrivate::handleSyncRequest(const QString &method, const QNetworkRequest &request,
        const QByteArray &data, QByteArray &result)
{
    QNetworkReply* reply = nullptr;
    if ("put" == method)
    {
        reply = networkManager->put(request, data);
    }
    else if ("post" == method)
    {
        reply = networkManager->post(request, data);
    }
    else
    {
        qCritical() << "Unknown method";
        return false;
    }
    QEventLoop loop;
    currentReply = reply;
    bool isOk;
    QObject::connect(reply, &QNetworkReply::finished, &loop, [reply, &isOk, &loop]()
    {
        isOk = (reply->error() == QNetworkReply::NoError);
        qInfo() << "network finished:" << reply->error();
        loop.quit();
    });
    if (m_timeOut > 0)
    {
        QTimer::singleShot(m_timeOut, &loop, [reply, &loop, &isOk]()
        {
            qCritical() << "network timeout!!!";
            isOk = false;
            reply->abort();
            loop.quit();
        });
    }
    loop.exec(QEventLoop::ExcludeUserInputEvents);

    result = reply->readAll();
    reply->deleteLater();
    currentReply = reply = nullptr;
    return isOk;
}

bool UHttpClientPrivate::handleASyncRequest(const QString &method, const QNetworkRequest &request,
        const QByteArray &data, std::function<void (int, QByteArray)> resopnse)
{
    Q_Q(UHttpClient);
    QNetworkReply* reply = nullptr;
    if ("put" == method)
    {
        reply = networkManager->put(request, data);
    }
    else if ("post" == method)
    {
        reply = networkManager->post(request, data);
    }
    else
    {
        qCritical() << "Unknown method";
        return false;
    }

    currentReply = reply;
    QObject::connect(reply, &QNetworkReply::finished, q, [this, method, reply, resopnse]()
    {
        qInfo() << method << "network finished:" << reply->error();
        QNetworkReply::NetworkError error = reply->error();
        QByteArray array = (error == QNetworkReply::NoError) ? reply->readAll() : QByteArray();
        if (resopnse)
        {
            resopnse(error, array);
        }
        reply->deleteLater();
        currentReply = nullptr;
    });

    return true;
}

UHttpClient* UHttpClient::instance()
{
    static UHttpClient client;
    return &client;
}

UHttpClient::UHttpClient(QObject* parent) : QObject(parent), d_ptr(new UHttpClientPrivate(this))
{
}

UHttpClient::~UHttpClient()
{
}

bool UHttpClient::getSync(const QString &url, QByteArray &resopnse, const QUrlQuery &urlQuery)
{
    Q_D(UHttpClient);
    if (url.isEmpty())
    {
        return false;
    }

    QNetworkRequest request;
    QString urlText = url;
    if (!urlQuery.isEmpty())
    {
        urlText = QString("%1?%2").arg(url).arg(urlQuery.toString());
    }
    request.setUrl(QUrl(urlText));

    QNetworkReply* reply = d->networkManager->get(request);
    d->currentReply = reply;
    QEventLoop loop;
    bool isOk = false;
    connect(reply, &QNetworkReply::finished, &loop, [reply, this, &loop, &isOk]()
    {
        isOk = (reply->error() == QNetworkReply::NoError);
        qInfo() << "network finished:" << reply->error();
        loop.quit();
    });

    if (d->m_timeOut > 0)
    {
        QTimer::singleShot(d->m_timeOut, &loop, [reply, &loop, &isOk]()
        {
            qCritical() << "network timeout!!!";
            isOk = false;
            reply->abort();
            loop.quit();
        });
    }
    loop.exec(QEventLoop::ExcludeUserInputEvents);

    if (isOk)
    {
        resopnse = reply->readAll();
    }
    else
    {
        resopnse.clear();
    }
    reply->deleteLater();
    d->currentReply = reply = nullptr;

    return isOk;
}

void UHttpClient::getAsync(const QString &url, std::function<void (int, QByteArray)> resopnse,
                           const QUrlQuery &urlQuery)
{
    Q_D(UHttpClient);
    if (url.isEmpty())
    {
        return;
    }

    QNetworkRequest request;
    QString urlText = url;
    if (!urlQuery.isEmpty())
    {
        urlText = QString("%1?%2").arg(url).arg(urlQuery.toString());
    }
    request.setUrl(QUrl(urlText));

    QNetworkReply* reply = d->networkManager->get(request);
    d->currentReply = reply;
    connect(reply, &QNetworkReply::finished, this, [reply, this, resopnse, d]()
    {
        QNetworkReply::NetworkError error = reply->error();
        QByteArray array = (error == QNetworkReply::NoError) ? reply->readAll() : QByteArray();
        if (resopnse)
        {
            resopnse(error, array);
        }
        reply->deleteLater();
        d->currentReply = nullptr;
    });
}

void UHttpClient::getAsync(const QString &url, std::function<void (int, QByteArray)> progress,
                           std::function<void (int, QByteArray)> finished, const QUrlQuery &urlQuery)
{
    Q_D(UHttpClient);
    if (url.isEmpty())
    {
        return;
    }

    QNetworkRequest request;
    QString urlText = url;
    if (!urlQuery.isEmpty())
    {
        urlText = QString("%1?%2").arg(url).arg(urlQuery.toString());
    }
    request.setUrl(QUrl(urlText));

    QNetworkReply* reply = d->networkManager->get(request);
    d->currentReply = reply;
    connect(reply, &QNetworkReply::downloadProgress, this, [ = ](qint64 bytesSent, qint64 bytesTotal)
    {
        if ((reply->error() == QNetworkReply::NoError)
            && (0 != bytesSent) && (0 != bytesTotal))
        {
            if (progress)
            {
                progress(reply->error(), reply->readAll());
            }
        }
    });
    connect(reply, &QNetworkReply::finished, this, [ = ]()
    {
        QNetworkReply::NetworkError error = reply->error();
        QByteArray array = (error == QNetworkReply::NoError) ? reply->readAll() : QByteArray();
        if (finished)
        {
            finished(error, array);
        }
        reply->deleteLater();
        d->currentReply = nullptr;
    });
}

bool UHttpClient::postSync(const QUrl &url, const QByteArray &data, QByteArray &result)
{
    Q_D(UHttpClient);
    if (url.isEmpty())
    {
        qCritical() << "data or url isEmpty" << url;
        return false;
    }

    QNetworkRequest request;
    request.setUrl(url);

    return d->handleSyncRequest("post", request, data, result);
}

void UHttpClient::postAsync(const QUrl &url, const QByteArray &data, std::function<void (int, QByteArray)> resopnse)
{
    Q_D(UHttpClient);
    if (url.isEmpty())
    {
        qCritical() << "data or url isEmpty";
        return;
    }

    QNetworkRequest request;
    request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
    request.setUrl(url);

    d->handleASyncRequest("post", request, data, resopnse);
}

bool UHttpClient::putSync(const QUrl &url, const QByteArray &data, QByteArray &result)
{
    Q_D(UHttpClient);
    if (url.isEmpty())
    {
        qCritical() << "data or url isEmpty";
        return false;
    }

    QNetworkRequest request;
    request.setUrl(url);

    return d->handleSyncRequest("put", request, data, result);
}

void UHttpClient::putAsync(const QUrl &url, const QByteArray &data, std::function<void (int, QByteArray)> resopnse)
{
    Q_D(UHttpClient);
    if (url.isEmpty())
    {
        qCritical() << "data or url isEmpty";
        return;
    }

    QNetworkRequest request;
    request.setUrl(url);

    d->handleASyncRequest("put", request, data, resopnse);
}

void UHttpClient::setTimeOut(int timeout)
{
    Q_D(UHttpClient);

    d->m_timeOut = timeout;
}

void UHttpClient::cancelRequest()
{
    Q_D(UHttpClient);

    if (nullptr != d->currentReply)
    {
        d->currentReply->abort();
        d->currentReply->deleteLater();
        d->currentReply = nullptr;
    }
}

bool UHttpClient::uploadFile(const QUrl &url, const QString &filePath)
{
    Q_D(UHttpClient);
    if (!QFile::exists(filePath) || !url.isValid())
    {
        qCritical() << "file is not exist or url isEmpty";
        return false;
    }
    QFileInfo upload_file_info(filePath);
    QFile* file = new QFile(filePath);
    if (!file->open(QIODevice::ReadOnly))
    {
        file->deleteLater();
        qCritical() << "file open failed!!!";
        return false;
    }

    QEventLoop loop;
    QHttpMultiPart* multiPart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
    QHttpPart filePart;
    filePart.setHeader(QNetworkRequest::ContentDispositionHeader, QVariant("form-data; name=\"file\"; filename=\"" + upload_file_info.fileName() + "\""));
    filePart.setHeader(QNetworkRequest::ContentTypeHeader, QVariant("application/octet-stream"));
    filePart.setBodyDevice(file);
    file->setParent(multiPart);
    multiPart->append(filePart);

    QNetworkRequest request;
    request.setHeader(QNetworkRequest::ContentTypeHeader, "multipart/form-data;boundary=" + multiPart->boundary());      //去掉boundary上的双引号
    request.setUrl(url);

    QNetworkReply* reply = d->networkManager->post(request, multiPart);
    multiPart->setParent(reply);
    d->currentReply = reply;
    connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit);
    loop.exec();
    reply->deleteLater();
    d->currentReply = reply = nullptr;
    return true;
}

bool UHttpClient::downloadFile(const QUrl &url, const QString &filePath, std::function<void (int)> resopnse)
{
    Q_D(UHttpClient);
    if (filePath.isEmpty() || !url.isValid())
    {
        qCritical() << "filePath isEmpty or url isEmpty";
        return false;
    }

    QFile* file = new QFile(filePath);
    if (!file->open(QIODevice::WriteOnly))
    {
        qCritical() << "file open failed!!!";
        file->deleteLater();
        return false;
    }

    QNetworkRequest request;
    request.setUrl(url);
    QNetworkReply* reply = d->networkManager->get(request);
    file->setParent(reply);
    connect(reply, &QNetworkReply::downloadProgress, this, [ = ](qint64 bytesSent, qint64 bytesTotal)
    {
        if ((reply->error() == QNetworkReply::NoError)
            && (0 != bytesSent) && (0 != bytesTotal))
        {
            file->write(reply->readAll());
        }
        qInfo() << "downloadProgress" << bytesSent << bytesTotal << reply->error();
    });
    connect(reply, &QNetworkReply::finished, this, [ = ]()
    {
        qInfo() << "download finished" << reply->error();
        if (reply->error() == QNetworkReply::NoError)
        {
            file->write(reply->readAll());
        }
        file->flush();
        file->close();
        reply->deleteLater();
        d->currentReply = nullptr;
        if (resopnse)
        {
            resopnse(reply->error());
        }
    });
    return true;
}


